﻿using System;
using System.Reflection;

namespace AppConfigVerifier
{
	public class TypeDefinition
	{
		public TypeDefinition(string typeName, AssemblyName assemblyName)
		{
			this.typeName = typeName;
			assembly = assemblyName;
		}

		readonly AssemblyName assembly;

		public AssemblyName AssemblyName
		{
			get
			{
				return assembly;
			}
		}

		readonly String typeName;

		public String TypeName
		{
			get
			{
				return typeName;
			}
		}
	}

	public static class Utility
	{
		public static TypeDefinition GetTypeDefinition(string type)
		{
			string[] typeNameSplit = type.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			return new TypeDefinition(typeNameSplit[0],
									  typeNameSplit.Length > 1
										? StrToAssemblyName(String.Join(",",
											typeNameSplit, 1, typeNameSplit.Length - 1).Trim())
										: null);
		}

		public static AssemblyName StrToAssemblyName(string strName)
		{
			if(String.IsNullOrEmpty(strName) || strName[0] == '\0')
			{
				return null;
			}
			return new AssemblyName(strName);
		}

		public class AssemblyNameMatchResult
		{
			static readonly AssemblyNameMatchResult _SuccessResult = new AssemblyNameMatchResult();
			public static AssemblyNameMatchResult SuccessResult
			{
				get
				{
					return _SuccessResult;
				}
			}
			private AssemblyNameMatchResult()
			{

			}
			public AssemblyNameMatchResult(AssemblyNameMismatch mismatch)
			{
				_Mistmatch = mismatch;
			}
			private readonly AssemblyNameMismatch _Mistmatch;
			public AssemblyNameMismatch Mistmatch
			{
				get
				{
					return _Mistmatch;
				}
			}
			private readonly bool _Success;
			public bool Success
			{
				get
				{
					return _Mistmatch == null;
				}
			}
		}

		[Flags]
		public enum MismatchType
		{
			Name = 0,
			Version = 1,
			Culture = 2,
			ProcessorArchitecture = 3,
			PublicToken = 4
		}
		public class AssemblyNameMismatch
		{
			public AssemblyNameMismatch(MismatchType mistmach, string thisValue, string otherAssemblyValue)
			{
				_MismatchType = mistmach;
				_ThisValue = thisValue;
				_OtherAssemblyValue = otherAssemblyValue;
			}
			private readonly MismatchType _MismatchType;
			public MismatchType MismatchType
			{
				get
				{
					return _MismatchType;
				}
			}
			private readonly string _ThisValue;
			public string ThisValue
			{
				get
				{
					return _ThisValue;
				}
			}
			private readonly string _OtherAssemblyValue;
			public string OtherAssemblyValue
			{
				get
				{
					return _OtherAssemblyValue;
				}
			}
		}

		private class AssemblyMatch
		{
			public AssemblyMatch(Func<AssemblyName, string> value, MismatchType type)
			{
				_Value = value;
				_MismatchType = type;
			}

			private readonly MismatchType _MismatchType;
			public MismatchType MismatchType
			{
				get
				{
					return _MismatchType;
				}
			}
			private readonly Func<AssemblyName, string> _Value;
			public Func<AssemblyName, string> Value
			{
				get
				{
					return _Value;
				}
			}
		}

		public static AssemblyNameMatchResult IsSubsetOf(this AssemblyName assembly, AssemblyName otherAssembly)
		{
			return Match(assembly, otherAssembly,
				new AssemblyMatch(a => a.Name.ToString(), MismatchType.Name),
				new AssemblyMatch(a => a.Version.ToString(), MismatchType.Version),
				new AssemblyMatch(a => a.CultureInfo.ThreeLetterISOLanguageName, MismatchType.Culture),
				new AssemblyMatch(a => a.ProcessorArchitecture.ToString(), MismatchType.ProcessorArchitecture),
				new AssemblyMatch(a => BitConverter.ToString(a.GetPublicKeyToken(), 0), MismatchType.PublicToken));
		}

		static AssemblyNameMatchResult Match(AssemblyName assemblyName, AssemblyName knownName,
			params AssemblyMatch[] matches)
		{
			foreach(var match in matches)
			{
				string assemblyValue = null;
				string comparedValue = null;
				try
				{
					assemblyValue = match.Value(assemblyName);
				}
				catch(NullReferenceException)
				{
				}
				catch(ArgumentNullException)
				{
				}

				if(assemblyValue != null && assemblyValue != "None")
				{
					try
					{
						comparedValue = match.Value(knownName);
					}
					catch(NullReferenceException)
					{
					}
					catch(ArgumentNullException)
					{
					}
					if(comparedValue != null)
					{
						if(comparedValue != assemblyValue)
							return new AssemblyNameMatchResult(new AssemblyNameMismatch(match.MismatchType, assemblyValue, comparedValue));
					}
				}
			}
			return AssemblyNameMatchResult.SuccessResult;
		}
	}
}
